{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import quandl\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import fbprophet\n",
    "import pytrends\n",
    "from pytrends.request import TrendReq\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 用于分析和尝试预测未来价格的类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Stocker():\n",
    "    \n",
    "    # 初始化需要股票代码\n",
    "    def __init__(self, ticker, exchange='WIKI'):\n",
    "        \n",
    "        # 强制资本化\n",
    "        ticker = ticker.upper()\n",
    "        \n",
    "        # 符号用来标记图\n",
    "        self.symbol = ticker\n",
    "        \n",
    "        # 使用Personal Api Key\n",
    "        # quandl.ApiConfig.api_key = 'YourKeyHere'\n",
    "\n",
    "        # 检索财务数据\n",
    "        try:\n",
    "            stock = quandl.get('%s/%s' % (exchange, ticker))\n",
    "        \n",
    "        except Exception as e:\n",
    "            print('Error Retrieving Data.')\n",
    "            print(e)\n",
    "            return\n",
    "        \n",
    "        # S将索引设置为名为Date的列\n",
    "        stock = stock.reset_index(level=0)\n",
    "        \n",
    "        # 需要的专栏\n",
    "        stock['ds'] = stock['Date']\n",
    "\n",
    "        if ('Adj. Close' not in stock.columns):\n",
    "            stock['Adj. Close'] = stock['Close']\n",
    "            stock['Adj. Open'] = stock['Open']\n",
    "        \n",
    "        stock['y'] = stock['Adj. Close']\n",
    "        stock['Daily Change'] = stock['Adj. Close'] - stock['Adj. Open']\n",
    "        \n",
    "        # 分配为类属性的数据\n",
    "        self.stock = stock.copy()\n",
    "        \n",
    "        # 范围内最小和最大日期\n",
    "        #self.min_date = '2015-01-01'\n",
    "        self.min_date = min(stock['Date'])\n",
    "        self.max_date = max(stock['Date'])\n",
    "        \n",
    "        # 查询最大和最小价格以及发生的日期\n",
    "        self.max_price = np.max(self.stock['y'])\n",
    "        self.min_price = np.min(self.stock['y'])\n",
    "        \n",
    "        self.min_price_date = self.stock[self.stock['y'] == self.min_price]['Date']\n",
    "        self.min_price_date = self.min_price_date[self.min_price_date.index[0]]\n",
    "        self.max_price_date = self.stock[self.stock['y'] == self.max_price]['Date']\n",
    "        self.max_price_date = self.max_price_date[self.max_price_date.index[0]]\n",
    "        \n",
    "        # 开盘价格\n",
    "        self.starting_price = float(self.stock.loc[0, 'Adj. Open'])\n",
    "        \n",
    "        # 最近价格\n",
    "        self.most_recent_price = float(self.stock.loc[self.stock.index[-1], 'y'])\n",
    "\n",
    "        # 是否圆形日期\n",
    "        self.round_dates = True\n",
    "        \n",
    "        # 需要训练的数据年份\n",
    "        self.training_years = 3\n",
    "\n",
    "        # Prophet参数，默认先于库\n",
    "        self.changepoint_prior_scale = 0.05 \n",
    "        self.weekly_seasonality = False\n",
    "        self.daily_seasonality = False\n",
    "        self.monthly_seasonality = True\n",
    "        self.yearly_seasonality = True\n",
    "        self.changepoints = None\n",
    "        \n",
    "        print('{} Stocker Initialized. Data covers {} to {}.'.format(self.symbol,\n",
    "                                                                     self.min_date,\n",
    "                                                                     self.max_date))\n",
    "    \n",
    "    \"\"\"\n",
    "    确保开始日期和结束日期在范围内，并且可以转换为pandas日期时间。 以正确的格式返回日期\n",
    "    \"\"\"\n",
    "    def handle_dates(self, start_date, end_date):\n",
    "        \n",
    "        \n",
    "        # 默认开始和结束日期是数据的开始和结束\n",
    "        if start_date is None:\n",
    "            start_date = self.min_date\n",
    "        if end_date is None:\n",
    "            end_date = self.max_date\n",
    "        \n",
    "        try:\n",
    "            # 转换为pandas datetime以索引数据帧\n",
    "            start_date = pd.to_datetime(start_date)\n",
    "            end_date = pd.to_datetime(end_date)\n",
    "        \n",
    "        except Exception as e:\n",
    "            print('Enter valid pandas date format.')\n",
    "            print(e)\n",
    "            return\n",
    "        \n",
    "        valid_start = False\n",
    "        valid_end = False\n",
    "        \n",
    "        # 用户将继续输入日期\n",
    "        while (not valid_start) & (not valid_end):\n",
    "            valid_end = True\n",
    "            valid_start = True\n",
    "            \n",
    "            if end_date < start_date:\n",
    "                print('结束日期必须晚于开始日期')\n",
    "                start_date = pd.to_datetime(input('输入新的开始日期：'))\n",
    "                end_date= pd.to_datetime(input('输入新的结束日期：'))\n",
    "                valid_end = False\n",
    "                valid_start = False\n",
    "            \n",
    "            else: \n",
    "                if end_date > self.max_date:\n",
    "                    print('结束日期超出数据范围')\n",
    "                    end_date= pd.to_datetime(input('输入洗的结束日期：'))\n",
    "                    valid_end = False\n",
    "\n",
    "                if start_date < self.min_date:\n",
    "                    print('开始日期在数据范围前')\n",
    "                    start_date = pd.to_datetime(input('输入新的开始日期：'))\n",
    "                    valid_start = False\n",
    "                \n",
    "        \n",
    "        return start_date, end_date\n",
    "        \n",
    "    \"\"\"\n",
    "    返回到指定范围的数据帧\n",
    "    \"\"\"\n",
    "    def make_df(self, start_date, end_date, df=None):\n",
    "        \n",
    "        # 默认是使用对象库存数据\n",
    "        if not df:\n",
    "            df = self.stock.copy()\n",
    "        \n",
    "        \n",
    "        start_date, end_date = self.handle_dates(start_date, end_date)\n",
    "        \n",
    "        # 记录开始和结束日期是否在数据中\n",
    "        start_in = True\n",
    "        end_in = True\n",
    "\n",
    "        # 如果用户想要舍入日期\n",
    "        if self.round_dates:\n",
    "            # 记录开始和结束日期是否为df\n",
    "            if (start_date not in list(df['Date'])):\n",
    "                start_in = False\n",
    "            if (end_date not in list(df['Date'])):\n",
    "                end_in = False\n",
    "\n",
    "            # 如果两者都不在数据框中，则舍入两者\n",
    "            if (not end_in) & (not start_in):\n",
    "                trim_df = df[(df['Date'] >= start_date) & \n",
    "                             (df['Date'] <= end_date)]\n",
    "            \n",
    "            else:\n",
    "                # 如果两者都在数据框中，则不会四舍五入\n",
    "                if (end_in) & (start_in):\n",
    "                    trim_df = df[(df['Date'] >= start_date) & \n",
    "                                 (df['Date'] <= end_date)]\n",
    "                else:\n",
    "                    # 如果只缺少start，则开始循环\n",
    "                    if (not start_in):\n",
    "                        trim_df = df[(df['Date'] > start_date) & \n",
    "                                     (df['Date'] <= end_date)]\n",
    "                    # 如果只是结束了圆形结束\n",
    "                    elif (not end_in):\n",
    "                        trim_df = df[(df['Date'] >= start_date) & \n",
    "                                     (df['Date'] < end_date)]\n",
    "\n",
    "        \n",
    "        else:\n",
    "            valid_start = False\n",
    "            valid_end = False\n",
    "            while (not valid_start) & (not valid_end):\n",
    "                start_date, end_date = self.handle_dates(start_date, end_date)\n",
    "                \n",
    "                # 没有圆形日期，如果数据不在，则打印消息并返回\n",
    "                if (start_date in list(df['Date'])):\n",
    "                    valid_start = True\n",
    "                if (end_date in list(df['Date'])):\n",
    "                    valid_end = True\n",
    "                    \n",
    "                # 检查以确保日期在数据中\n",
    "                if (start_date not in list(df['Date'])):\n",
    "                    print('开始日期不在数据中（超出范围或不在交易日）。')\n",
    "                    start_date = pd.to_datetime(input(prompt='输入新的开始日期：'))\n",
    "                    \n",
    "                elif (end_date not in list(df['Date'])):\n",
    "                    print('结束日期不在数据中（超出范围或不在交易日）。')\n",
    "                    end_date = pd.to_datetime(input(prompt='输入新的结束日期：') )\n",
    "\n",
    "            # 日期不是四舍五入\n",
    "            trim_df = df[(df['Date'] >= start_date) & \n",
    "                         (df['Date'] <= end_date.date)]\n",
    "\n",
    "        \n",
    "            \n",
    "        return trim_df\n",
    "\n",
    "\n",
    "    # 基本历史图和基本统计\n",
    "    def plot_stock(self, start_date=None, end_date=None, stats=['Adj. Close'], plot_type='basic'):\n",
    "        \n",
    "        self.reset_plot()\n",
    "        \n",
    "        if start_date is None:\n",
    "            start_date = self.min_date\n",
    "        if end_date is None:\n",
    "            end_date = self.max_date\n",
    "        \n",
    "        stock_plot = self.make_df(start_date, end_date)\n",
    "\n",
    "        colors = ['r', 'b', 'g', 'y', 'c', 'm']\n",
    "        \n",
    "        for i, stat in enumerate(stats):\n",
    "            \n",
    "            stat_min = min(stock_plot[stat])\n",
    "            stat_max = max(stock_plot[stat])\n",
    "\n",
    "            stat_avg = np.mean(stock_plot[stat])\n",
    "            \n",
    "            date_stat_min = stock_plot[stock_plot[stat] == stat_min]['Date']\n",
    "            date_stat_min = date_stat_min[date_stat_min.index[0]]\n",
    "            date_stat_max = stock_plot[stock_plot[stat] == stat_max]['Date']\n",
    "            date_stat_max = date_stat_max[date_stat_max.index[0]]\n",
    "            \n",
    "            print('Maximum {} = {:.2f} on {}.'.format(stat, stat_max, date_stat_max))\n",
    "            print('Minimum {} = {:.2f} on {}.'.format(stat, stat_min, date_stat_min))\n",
    "            print('Current {} = {:.2f} on {}.\\n'.format(stat, self.stock.loc[self.stock.index[-1], stat], self.max_date))\n",
    "            \n",
    "            # Percentage y轴\n",
    "            if plot_type == 'pct':\n",
    "                # 简单情节\n",
    "                plt.style.use('fivethirtyeight');\n",
    "                if stat == 'Daily Change':\n",
    "                    plt.plot(stock_plot['Date'], 100 * stock_plot[stat],\n",
    "                         color = colors[i], linewidth = 2.4, alpha = 0.9,\n",
    "                         label = stat)\n",
    "                else:\n",
    "                    plt.plot(stock_plot['Date'], 100 * (stock_plot[stat] -  stat_avg) / stat_avg,\n",
    "                         color = colors[i], linewidth = 2.4, alpha = 0.9,\n",
    "                         label = stat)\n",
    "\n",
    "                plt.xlabel('Date'); plt.ylabel('Change Relative to Average (%)'); plt.title('%s Stock History' % self.symbol); \n",
    "                plt.legend(prop={'size':10})\n",
    "                plt.grid(color = 'k', alpha = 0.4); \n",
    "\n",
    "            # Stat y轴\n",
    "            elif plot_type == 'basic':\n",
    "                plt.style.use('fivethirtyeight');\n",
    "                plt.plot(stock_plot['Date'], stock_plot[stat], color = colors[i], linewidth = 3, label = stat, alpha = 0.8)\n",
    "                plt.xlabel('Date'); plt.ylabel('US $'); plt.title('%s Stock History' % self.symbol); \n",
    "                plt.legend(prop={'size':10})\n",
    "                plt.grid(color = 'k', alpha = 0.4); \n",
    "      \n",
    "        plt.show();\n",
    "        \n",
    "    #重置绘图参数以清除样式格式\n",
    "    @staticmethod\n",
    "    def reset_plot():\n",
    "        \n",
    "        #恢复默认参数\n",
    "        matplotlib.rcdefaults()\n",
    "        \n",
    "        #调整参数\n",
    "        matplotlib.rcParams['figure.figsize'] = (8, 5)\n",
    "        matplotlib.rcParams['axes.labelsize'] = 10\n",
    "        matplotlib.rcParams['xtick.labelsize'] = 8\n",
    "        matplotlib.rcParams['ytick.labelsize'] = 8\n",
    "        matplotlib.rcParams['axes.titlesize'] = 14\n",
    "        matplotlib.rcParams['text.color'] = 'k'\n",
    "    \n",
    "    # 在周末线性插入价格的方法\n",
    "    def resample(self, dataframe):\n",
    "        # Change the index and resample at daily level\n",
    "        dataframe = dataframe.set_index('ds')\n",
    "        dataframe = dataframe.resample('D')\n",
    "        \n",
    "        # 重置索引并插入nan值\n",
    "        dataframe = dataframe.reset_index(level=0)\n",
    "        dataframe = dataframe.interpolate()\n",
    "        return dataframe\n",
    "    \n",
    "    #在数据框中删除周末\n",
    "    def remove_weekends(self, dataframe):\n",
    "        \n",
    "        #重置索引以使用ix\n",
    "        dataframe = dataframe.reset_index(drop=True)\n",
    "        \n",
    "        weekends = []\n",
    "        \n",
    "        # 查找所有周末\n",
    "        for i, date in enumerate(dataframe['ds']):\n",
    "            if (date.weekday()) == 5 | (date.weekday() == 6):\n",
    "                weekends.append(i)\n",
    "            \n",
    "        #舍弃周末\n",
    "        dataframe = dataframe.drop(weekends, axis=0)\n",
    "        \n",
    "        return dataframe\n",
    "    \n",
    "    \n",
    "    #计算并计划在指定日期范围内购买和持有股票的利润\n",
    "    def buy_and_hold(self, start_date=None, end_date=None, nshares=1):\n",
    "        self.reset_plot()\n",
    "        \n",
    "        start_date, end_date = self.handle_dates(start_date, end_date)\n",
    "            \n",
    "        # 查找股票的起始和结束价格\n",
    "        start_price = float(self.stock[self.stock['Date'] == start_date]['Adj. Open'])\n",
    "        end_price = float(self.stock[self.stock['Date'] == end_date]['Adj. Close'])\n",
    "        \n",
    "        #创建利润数据框并计算利润列\n",
    "        profits = self.make_df(start_date, end_date)\n",
    "        profits['hold_profit'] = nshares * (profits['Adj. Close'] - start_price)\n",
    "        \n",
    "        #总利润\n",
    "        total_hold_profit = nshares * (end_price - start_price)\n",
    "        \n",
    "        print('{} Total buy and hold profit from {} to {} for {} shares = ${:.2f}'.format\n",
    "              (self.symbol, start_date, end_date, nshares, total_hold_profit))\n",
    "        \n",
    "        #绘图\n",
    "        plt.style.use('dark_background')\n",
    "        \n",
    "        # 利润数量的位置\n",
    "        text_location = (end_date - pd.DateOffset(months = 1))\n",
    "        \n",
    "        #绘制利润随时间变化的图像\n",
    "        plt.plot(profits['Date'], profits['hold_profit'], 'b', linewidth = 3)\n",
    "        plt.ylabel('Profit ($)'); plt.xlabel('Date'); plt.title('Buy and Hold Profits for {} {} to {}'.format(\n",
    "                                                                self.symbol, start_date, end_date))\n",
    "        \n",
    "        #在图上显示最终值\n",
    "        plt.text(x = text_location, \n",
    "             y =  total_hold_profit + (total_hold_profit / 40),\n",
    "             s = '$%d' % total_hold_profit,\n",
    "            color = 'g' if total_hold_profit > 0 else 'r',\n",
    "            size = 14)\n",
    "        \n",
    "        plt.grid(alpha=0.2)\n",
    "        plt.show();\n",
    "        \n",
    "    # 创建一个没有训练过的模型\n",
    "    def create_model(self):\n",
    "\n",
    "        #制作模型\n",
    "        model = fbprophet.Prophet(daily_seasonality=self.daily_seasonality,  \n",
    "                                  weekly_seasonality=self.weekly_seasonality, \n",
    "                                  yearly_seasonality=self.yearly_seasonality,\n",
    "                                  changepoint_prior_scale=self.changepoint_prior_scale,\n",
    "                                  changepoints=self.changepoints)\n",
    "        \n",
    "        if self.monthly_seasonality:\n",
    "            # 添加每月季节性\n",
    "            model.add_seasonality(name = 'monthly', period = 30.5, fourier_order = 5)\n",
    "        \n",
    "        return model\n",
    "    \n",
    "    #绘制改变先前比例变化点（cps）的效果\n",
    "    def changepoint_prior_analysis(self, changepoint_priors=[0.001, 0.05, 0.1, 0.2], colors=['b', 'r', 'grey', 'gold']):\n",
    "    \n",
    "        #使用指定年份的数据进行培训和绘图\n",
    "        train = self.stock[(self.stock['Date'] > (max(self.stock['Date']) - pd.DateOffset(years=self.training_years)))]\n",
    "        \n",
    "        #迭代所有的变更点并制作模型\n",
    "        for i, prior in enumerate(changepoint_priors):\n",
    "            # 选择变更点\n",
    "            self.changepoint_prior_scale = prior\n",
    "            \n",
    "            #使用指定的cps创建并训练模型\n",
    "            model = self.create_model()\n",
    "            model.fit(train)\n",
    "            future = model.make_future_dataframe(periods=180, freq='D')\n",
    "            \n",
    "            #制作一个数据框来保存预测\n",
    "            if i == 0:\n",
    "                predictions = future.copy()\n",
    "                \n",
    "            future = model.predict(future)\n",
    "            \n",
    "            #填写预测数据框\n",
    "            predictions['%.3f_yhat_upper' % prior] = future['yhat_upper']\n",
    "            predictions['%.3f_yhat_lower' % prior] = future['yhat_lower']\n",
    "            predictions['%.3f_yhat' % prior] = future['yhat']\n",
    "         \n",
    "        # 移除周末\n",
    "        predictions = self.remove_weekends(predictions)\n",
    "        \n",
    "        # 绘图设置\n",
    "        self.reset_plot()\n",
    "        plt.style.use('fivethirtyeight')\n",
    "        fig, ax = plt.subplots(1, 1)\n",
    "        \n",
    "        # 实际观察\n",
    "        ax.plot(train['ds'], train['y'], 'ko', ms = 4, label = 'Observations')\n",
    "        color_dict = {prior: color for prior, color in zip(changepoint_priors, colors)}\n",
    "\n",
    "        # 绘制每个变更点的预测\n",
    "        for prior in changepoint_priors:\n",
    "            # 绘制预测本身\n",
    "            ax.plot(predictions['ds'], predictions['%.3f_yhat' % prior], linewidth = 1.2,\n",
    "                     color = color_dict[prior], label = '%.3f prior scale' % prior)\n",
    "            \n",
    "            # 不确定区间\n",
    "            ax.fill_between(predictions['ds'].dt.to_pydatetime(), predictions['%.3f_yhat_upper' % prior],\n",
    "                            predictions['%.3f_yhat_lower' % prior], facecolor = color_dict[prior],\n",
    "                            alpha = 0.3, edgecolor = 'k', linewidth = 0.6)\n",
    "                            \n",
    "        # 标签\n",
    "        plt.legend(loc = 2, prop={'size': 10})\n",
    "        plt.xlabel('Date'); plt.ylabel('Stock Price ($)'); plt.title('Effect of Changepoint Prior Scale');\n",
    "        plt.show()\n",
    "            \n",
    "    # 指定天数的基本模型\n",
    "    def create_prophet_model(self, days=0, resample=False):\n",
    "        \n",
    "        self.reset_plot()\n",
    "        \n",
    "        model = self.create_model()\n",
    "        \n",
    "        # 适合self.training_years的股票历史记录\n",
    "        stock_history = self.stock[self.stock['Date'] > (self.max_date - pd.DateOffset(years = self.training_years))]\n",
    "        \n",
    "        if resample:\n",
    "            stock_history = self.resample(stock_history)\n",
    "        \n",
    "        model.fit(stock_history)\n",
    "        \n",
    "        # 使用未来的数据框制作并预测明年\n",
    "        future = model.make_future_dataframe(periods = days, freq='D')\n",
    "        future = model.predict(future)\n",
    "        \n",
    "        if days > 0:\n",
    "            # 打印预测价格\n",
    "            print('Predicted Price on {} = ${:.2f}'.format(\n",
    "                future.loc[future.index[-1], 'ds'], future.loc[future.index[-1], 'yhat']))\n",
    "\n",
    "            title = '%s Historical and Predicted Stock Price'  % self.symbol\n",
    "        else:\n",
    "            title = '%s Historical and Modeled Stock Price' % self.symbol\n",
    "        \n",
    "        #设置图\n",
    "        fig, ax = plt.subplots(1, 1)\n",
    "\n",
    "        # 实际值\n",
    "        ax.plot(stock_history['ds'], stock_history['y'], 'ko-', linewidth = 1.4, alpha = 0.8, ms = 1.8, label = 'Observations')\n",
    "        \n",
    "        # 预测值\n",
    "        ax.plot(future['ds'], future['yhat'], 'forestgreen',linewidth = 2.4, label = 'Modeled');\n",
    "\n",
    "        #不定区间绘制为色带\n",
    "        ax.fill_between(future['ds'].dt.to_pydatetime(), future['yhat_upper'], future['yhat_lower'], alpha = 0.3, \n",
    "                       facecolor = 'g', edgecolor = 'k', linewidth = 1.4, label = 'Confidence Interval')\n",
    "\n",
    "        # 格式\n",
    "        plt.legend(loc = 2, prop={'size': 10}); plt.xlabel('Date'); plt.ylabel('Price $');\n",
    "        plt.grid(linewidth=0.6, alpha = 0.6)\n",
    "        plt.title(title);\n",
    "        plt.show()\n",
    "        \n",
    "        return model, future\n",
    "      \n",
    "    #评估预测模型一年\n",
    "    def evaluate_prediction(self, start_date=None, end_date=None, nshares = None):\n",
    "        \n",
    "        # 默认开始日期比数据的结束日期早一年，结束日期是数据的结束日期\n",
    "        if start_date is None:\n",
    "            start_date = self.max_date - pd.DateOffset(years=1)\n",
    "        if end_date is None:\n",
    "            end_date = self.max_date\n",
    "            \n",
    "        start_date, end_date = self.handle_dates(start_date, end_date)\n",
    "        \n",
    "        # 培训数据在开始日期之前的几年开始self.training_years，并开始上升到开始日期\n",
    "        train = self.stock[(self.stock['Date'] < start_date) & \n",
    "                           (self.stock['Date'] > (start_date - pd.DateOffset(years=self.training_years)))]\n",
    "        \n",
    "        #测试数据在范围内指定\n",
    "        test = self.stock[(self.stock['Date'] >= start_date) & (self.stock['Date'] <= end_date)]\n",
    "        \n",
    "        #创建并训练模型\n",
    "        model = self.create_model()\n",
    "        model.fit(train)\n",
    "        \n",
    "        #预测数据额的数据框和预测\n",
    "        future = model.make_future_dataframe(periods = 365, freq='D')\n",
    "        future = model.predict(future)\n",
    "        \n",
    "        #使用已知值合并预测\n",
    "        test = pd.merge(test, future, on = 'ds', how = 'inner')\n",
    "\n",
    "        train = pd.merge(train, future, on = 'ds', how = 'inner')\n",
    "        \n",
    "        #计算连续测量之间的差异\n",
    "        test['pred_diff'] = test['yhat'].diff()\n",
    "        test['real_diff'] = test['y'].diff()\n",
    "\n",
    "        #当我们预测正确时，显示正确\n",
    "        test['correct'] = (np.sign(test['pred_diff'][1:]) == np.sign(test['real_diff'][1:])) * 1\n",
    "        \n",
    "        # 预测增加或者减少的准确性\n",
    "        increase_accuracy = 100 * np.mean(test[test['pred_diff'] > 0]['correct'])\n",
    "        decrease_accuracy = 100 * np.mean(test[test['pred_diff'] < 0]['correct'])\n",
    "\n",
    "        # 平均绝对误差\n",
    "        test_errors = abs(test['y'] - test['yhat'])\n",
    "        test_mean_error = np.mean(test_errors)\n",
    "\n",
    "        train_errors = abs(train['y'] - train['yhat'])\n",
    "        train_mean_error = np.mean(train_errors)\n",
    "\n",
    "        #计算预测范围内实际值的时间百分比\n",
    "        test['in_range'] = False\n",
    "\n",
    "        for i in test.index:\n",
    "            if (test.loc[i, 'y'] < test.loc[i, 'yhat_upper']) & (test.loc[i, 'y'] > test.loc[i, 'yhat_lower']):\n",
    "                test.loc[i, 'in_range'] = True\n",
    "\n",
    "        in_range_accuracy = 100 * np.mean(test['in_range'])\n",
    "\n",
    "        if not nshares:\n",
    "\n",
    "            # 日期预测范围\n",
    "            print('\\n Prediction Range: {} to {}.'.format(start_date,\n",
    "                end_date))\n",
    "\n",
    "            # 最终预测和实际值\n",
    "            print('\\n Predicted price on {} = ${:.2f}.'.format(max(future['ds']), future.loc[future.index[-1], 'yhat']))\n",
    "            print('Actual price on    {} = ${:.2f}.\\n'.format(max(test['ds']), test.loc[test.index[-1], 'y']))\n",
    "\n",
    "            print('Average Absolute Error on Training Data = ${:.2f}.'.format(train_mean_error))\n",
    "            print('Average Absolute Error on Testing  Data = ${:.2f}.\\n'.format(test_mean_error))\n",
    "\n",
    "            #精度\n",
    "            print('When the model predicted an increase, the price increased {:.2f}% of the time.'.format(increase_accuracy))\n",
    "            print('When the model predicted a  decrease, the price decreased  {:.2f}% of the time.\\n'.format(decrease_accuracy))\n",
    "\n",
    "            print('The actual value was within the {:d}% confidence interval {:.2f}% of the time.'.format(int(100 * model.interval_width), in_range_accuracy))\n",
    "\n",
    "\n",
    "             #重置\n",
    "            self.reset_plot()\n",
    "            \n",
    "            #设置\n",
    "            fig, ax = plt.subplots(1, 1)\n",
    "\n",
    "            #实际值\n",
    "            ax.plot(train['ds'], train['y'], 'ko-', linewidth = 1.4, alpha = 0.8, ms = 1.8, label = 'Observations')\n",
    "            ax.plot(test['ds'], test['y'], 'ko-', linewidth = 1.4, alpha = 0.8, ms = 1.8, label = 'Observations')\n",
    "            \n",
    "            #预测值\n",
    "            ax.plot(future['ds'], future['yhat'], 'navy', linewidth = 2.4, label = 'Predicted');\n",
    "\n",
    "            #不确定区域为灰色地带\n",
    "            ax.fill_between(future['ds'].dt.to_pydatetime(), future['yhat_upper'], future['yhat_lower'], alpha = 0.6, \n",
    "                           facecolor = 'gold', edgecolor = 'k', linewidth = 1.4, label = 'Confidence Interval')\n",
    "\n",
    "            # 在预测开始处放置一条垂直线\n",
    "            plt.vlines(x=min(test['ds']), ymin=min(future['yhat_lower']), ymax=max(future['yhat_upper']), colors = 'r',\n",
    "                       linestyles='dashed', label = 'Prediction Start')\n",
    "\n",
    "            # 格式\n",
    "            plt.legend(loc = 2, prop={'size': 8}); plt.xlabel('Date'); plt.ylabel('Price $');\n",
    "            plt.grid(linewidth=0.6, alpha = 0.6)\n",
    "                       \n",
    "            plt.title('{} Model Evaluation from {} to {}.'.format(self.symbol,\n",
    "                start_date, end_date));\n",
    "            plt.show();\n",
    "\n",
    "        \n",
    "        # 指定了多个份额\n",
    "        elif nshares:\n",
    "            \n",
    "            #只有这个股票会增长的时候我们会炒股\n",
    "            test_pred_increase = test[test['pred_diff'] > 0]\n",
    "            \n",
    "            test_pred_increase.reset_index(inplace=True)\n",
    "            prediction_profit = []\n",
    "            \n",
    "            #迭代所有的预测并且计算炒股的利润\n",
    "            for i, correct in enumerate(test_pred_increase['correct']):\n",
    "                \n",
    "                #如果我们预测到并且价格上涨，我们就会获得差额\n",
    "                if correct == 1:\n",
    "                    prediction_profit.append(nshares * test_pred_increase.loc[i, 'real_diff'])\n",
    "                # 如果我们预测到价格下降，我们会失去差额\n",
    "                else:\n",
    "                    prediction_profit.append(nshares * test_pred_increase.loc[i, 'real_diff'])\n",
    "            \n",
    "            test_pred_increase['pred_profit'] = prediction_profit\n",
    "            \n",
    "            # 把利润导入测试数据框中\n",
    "            test = pd.merge(test, test_pred_increase[['ds', 'pred_profit']], on = 'ds', how = 'left')\n",
    "            test.loc[0, 'pred_profit'] = 0\n",
    "        \n",
    "            #任何一种方法在任何日期的利润\n",
    "            test['pred_profit'] = test['pred_profit'].cumsum().ffill()\n",
    "            test['hold_profit'] = nshares * (test['y'] - float(test.loc[0, 'y']))\n",
    "            \n",
    "            #显示信息\n",
    "            print('You played the stock market in {} from {} to {} with {} shares.\\n'.format(\n",
    "                self.symbol, start_date, end_date, nshares))\n",
    "            \n",
    "            print('When the model predicted an increase, the price increased {:.2f}% of the time.'.format(increase_accuracy))\n",
    "            print('When the model predicted a  decrease, the price decreased  {:.2f}% of the time.\\n'.format(decrease_accuracy))\n",
    "\n",
    "            # 显示有关股票市场风险的一些良性信息\n",
    "            print('The total profit using the Prophet model = ${:.2f}.'.format(np.sum(prediction_profit)))\n",
    "            print('The Buy and Hold strategy profit =         ${:.2f}.'.format(float(test.loc[test.index[-1], 'hold_profit'])))\n",
    "            print('\\nThanks for playing the stock market!\\n')\n",
    "            \n",
    "           \n",
    "            \n",
    "            #绘制预测和实际利润随时间变化的情况\n",
    "            self.reset_plot()\n",
    "            \n",
    "            #最终的利润和最终的智能用于定位文本\n",
    "            final_profit = test.loc[test.index[-1], 'pred_profit']\n",
    "            final_smart = test.loc[test.index[-1], 'hold_profit']\n",
    "\n",
    "            #文本\n",
    "            last_date = test.loc[test.index[-1], 'ds']\n",
    "            text_location = (last_date - pd.DateOffset(months = 1))\n",
    "\n",
    "            plt.style.use('dark_background')\n",
    "\n",
    "            #智能利润\n",
    "            plt.plot(test['ds'], test['hold_profit'], 'b',\n",
    "                     linewidth = 1.8, label = 'Buy and Hold Strategy') \n",
    "\n",
    "            #预测利润\n",
    "            plt.plot(test['ds'], test['pred_profit'], \n",
    "                     color = 'g' if final_profit > 0 else 'r',\n",
    "                     linewidth = 1.8, label = 'Prediction Strategy')\n",
    "\n",
    "            # 显示最终值\n",
    "            plt.text(x = text_location, \n",
    "                     y =  final_profit + (final_profit / 40),\n",
    "                     s = '$%d' % final_profit,\n",
    "                    color = 'g' if final_profit > 0 else 'r',\n",
    "                    size = 18)\n",
    "            \n",
    "            plt.text(x = text_location, \n",
    "                     y =  final_smart + (final_smart / 40),\n",
    "                     s = '$%d' % final_smart,\n",
    "                    color = 'g' if final_smart > 0 else 'r',\n",
    "                    size = 18);\n",
    "\n",
    "            plt.ylabel('Profit  (US $)'); plt.xlabel('Date'); \n",
    "            plt.title('Predicted versus Buy and Hold Profits');\n",
    "            plt.legend(loc = 2, prop={'size': 10});\n",
    "            plt.grid(alpha=0.2); \n",
    "            plt.show()\n",
    "        \n",
    "    def retrieve_google_trends(self, search, date_range):\n",
    "        \n",
    "        # 设置趋势提取对象\n",
    "        pytrends = TrendReq(hl='en-US', tz=360)\n",
    "        kw_list = [search]\n",
    "\n",
    "        try:\n",
    "        \n",
    "            # 创建搜索对象\n",
    "            pytrends.build_payload(kw_list, cat=0, timeframe=date_range[0], geo='', gprop='news')\n",
    "            \n",
    "            #随时检索\n",
    "            trends = pytrends.interest_over_time()\n",
    "\n",
    "            related_queries = pytrends.related_queries()\n",
    "\n",
    "        except Exception as e:\n",
    "            print('\\nGoogle Search Trend retrieval failed.')\n",
    "            print(e)\n",
    "            return\n",
    "        \n",
    "        return trends, related_queries\n",
    "        \n",
    "    def changepoint_date_analysis(self, search=None):\n",
    "        self.reset_plot()\n",
    "\n",
    "        model = self.create_model()\n",
    "        \n",
    "        #使用过去self.training_years年的数据\n",
    "        train = self.stock[self.stock['Date'] > (self.max_date - pd.DateOffset(years = self.training_years))]\n",
    "        model.fit(train)\n",
    "        \n",
    "        #训练数据的预测（没有未来时期）\n",
    "        future = model.make_future_dataframe(periods=0, freq='D')\n",
    "        future = model.predict(future)\n",
    "    \n",
    "        train = pd.merge(train, future[['ds', 'yhat']], on = 'ds', how = 'inner')\n",
    "        \n",
    "        changepoints = model.changepoints\n",
    "        train = train.reset_index(drop=True)\n",
    "        \n",
    "        # 仅创建变更点的数据框\n",
    "        change_indices = []\n",
    "        for changepoint in (changepoints):\n",
    "            change_indices.append(train[train['ds'] == changepoint].index[0])\n",
    "        \n",
    "        c_data = train.loc[change_indices, :]\n",
    "        deltas = model.params['delta'][0]\n",
    "        \n",
    "        c_data['delta'] = deltas\n",
    "        c_data['abs_delta'] = abs(c_data['delta'])\n",
    "        \n",
    "        # 按最大变化对值进行排序\n",
    "        c_data = c_data.sort_values(by='abs_delta', ascending=False)\n",
    "\n",
    "        #限制为10个最大的变更点\n",
    "        c_data = c_data[:10]\n",
    "\n",
    "        # 分为负面和正面变化点\n",
    "        cpos_data = c_data[c_data['delta'] > 0]\n",
    "        cneg_data = c_data[c_data['delta'] < 0]\n",
    "\n",
    "        # 变更点和数据\n",
    "        if not search:\n",
    "        \n",
    "            print('\\nChangepoints sorted by slope rate of change (2nd derivative):\\n')\n",
    "            print(c_data.loc[:, ['Date', 'Adj. Close', 'delta']][:5])\n",
    "\n",
    "            # 线图显示实际值，估计值和变更点\n",
    "            self.reset_plot()\n",
    "            \n",
    "            # 设置线图\n",
    "            plt.plot(train['ds'], train['y'], 'ko', ms = 4, label = 'Stock Price')\n",
    "            plt.plot(future['ds'], future['yhat'], color = 'navy', linewidth = 2.0, label = 'Modeled')\n",
    "            \n",
    "            # 将点更改为垂直线\n",
    "            plt.vlines(cpos_data['ds'].dt.to_pydatetime(), ymin = min(train['y']), ymax = max(train['y']), \n",
    "                       linestyles='dashed', color = 'r', \n",
    "                       linewidth= 1.2, label='Negative Changepoints')\n",
    "\n",
    "            plt.vlines(cneg_data['ds'].dt.to_pydatetime(), ymin = min(train['y']), ymax = max(train['y']), \n",
    "                       linestyles='dashed', color = 'darkgreen', \n",
    "                       linewidth= 1.2, label='Positive Changepoints')\n",
    "\n",
    "            plt.legend(prop={'size':10});\n",
    "            plt.xlabel('Date'); plt.ylabel('Price ($)'); plt.title('Stock Price with Changepoints')\n",
    "            plt.show()\n",
    "        \n",
    "        #在Google新闻中搜索搜索字词,显示相关查询，上升相关查询,图形变化点，搜索频率，股票价格\n",
    "        if search:\n",
    "            date_range = ['%s %s' % (str(min(train['Date'])), str(max(train['Date'])))]\n",
    "\n",
    "            #获取指定字词的Google趋势并加入培训数据框\n",
    "            trends, related_queries = self.retrieve_google_trends(search, date_range)\n",
    "\n",
    "            if (trends is None)  or (related_queries is None):\n",
    "                print('No search trends found for %s' % search)\n",
    "                return\n",
    "\n",
    "            print('\\n Top Related Queries: \\n')\n",
    "            print(related_queries[search]['top'].head())\n",
    "\n",
    "            print('\\n Rising Related Queries: \\n')\n",
    "            print(related_queries[search]['rising'].head())\n",
    "\n",
    "            #上传用于加入训练数据的数据\n",
    "            trends = trends.resample('D')\n",
    "\n",
    "            trends = trends.reset_index(level=0)\n",
    "            trends = trends.rename(columns={'date': 'ds', search: 'freq'})\n",
    "\n",
    "            # 插值频率\n",
    "            trends['freq'] = trends['freq'].interpolate()\n",
    "\n",
    "            #合并训练数据\n",
    "            train = pd.merge(train, trends, on = 'ds', how = 'inner')\n",
    "\n",
    "            # 标准化\n",
    "            train['y_norm'] = train['y'] / max(train['y'])\n",
    "            train['freq_norm'] = train['freq'] / max(train['freq'])\n",
    "            \n",
    "            self.reset_plot()\n",
    "\n",
    "            #绘制标准化股票价格并规范搜索频率\n",
    "            plt.plot(train['ds'], train['y_norm'], 'k-', label = 'Stock Price')\n",
    "            plt.plot(train['ds'], train['freq_norm'], color='goldenrod', label = 'Search Frequency')\n",
    "\n",
    "            # 将点更改为垂直线\n",
    "            plt.vlines(cpos_data['ds'].dt.to_pydatetime(), ymin = 0, ymax = 1, \n",
    "                       linestyles='dashed', color = 'r', \n",
    "                       linewidth= 1.2, label='Negative Changepoints')\n",
    "\n",
    "            plt.vlines(cneg_data['ds'].dt.to_pydatetime(), ymin = 0, ymax = 1, \n",
    "                       linestyles='dashed', color = 'darkgreen', \n",
    "                       linewidth= 1.2, label='Positive Changepoints')\n",
    "\n",
    "            plt.legend(prop={'size': 10})\n",
    "            plt.xlabel('Date'); plt.ylabel('Normalized Values'); plt.title('%s Stock Price and Search Frequency for %s' % (self.symbol, search))\n",
    "            plt.show()\n",
    "        \n",
    "    # 预测给定天数的未来价格\n",
    "    def predict_future(self, days=30):\n",
    "        \n",
    "        #使用过去的self.training_years年份进行培训\n",
    "        train = self.stock[self.stock['Date'] > (max(self.stock['Date']) - pd.DateOffset(years=self.training_years))]\n",
    "        \n",
    "        model = self.create_model()\n",
    "        \n",
    "        model.fit(train)\n",
    "        \n",
    "        # 具有指定预测天数的未来数据帧\n",
    "        future = model.make_future_dataframe(periods=days, freq='D')\n",
    "        future = model.predict(future)\n",
    "        \n",
    "        # 仅关注未来日期\n",
    "        future = future[future['ds'] >= max(self.stock['Date'])]\n",
    "        \n",
    "        #移出周末\n",
    "        future = self.remove_weekends(future)\n",
    "        \n",
    "        #计算是否增加\n",
    "        future['diff'] = future['yhat'].diff()\n",
    "    \n",
    "        future = future.dropna()\n",
    "\n",
    "        # 找到预测方向并创建单独的数据帧\n",
    "        future['direction'] = (future['diff'] > 0) * 1\n",
    "        \n",
    "        #重命名列以进行演示\n",
    "        future = future.rename(columns={'ds': 'Date', 'yhat': 'estimate', 'diff': 'change', \n",
    "                                        'yhat_upper': 'upper', 'yhat_lower': 'lower'})\n",
    "        \n",
    "        future_increase = future[future['direction'] == 1]\n",
    "        future_decrease = future[future['direction'] == 0]\n",
    "        \n",
    "        # 打印出日期\n",
    "        print('\\nPredicted Increase: \\n')\n",
    "        print(future_increase[['Date', 'estimate', 'change', 'upper', 'lower']])\n",
    "        \n",
    "        print('\\nPredicted Decrease: \\n')\n",
    "        print(future_decrease[['Date', 'estimate', 'change', 'upper', 'lower']])\n",
    "        \n",
    "        self.reset_plot()\n",
    "        \n",
    "        plt.style.use('fivethirtyeight')\n",
    "        matplotlib.rcParams['axes.labelsize'] = 10\n",
    "        matplotlib.rcParams['xtick.labelsize'] = 8\n",
    "        matplotlib.rcParams['ytick.labelsize'] = 8\n",
    "        matplotlib.rcParams['axes.titlesize'] = 12\n",
    "        \n",
    "        # 绘制预测并指出是增加还是减少\n",
    "        fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n",
    "\n",
    "        # 绘制估算值\n",
    "        ax.plot(future_increase['Date'], future_increase['estimate'], 'g^', ms = 12, label = 'Pred. Increase')\n",
    "        ax.plot(future_decrease['Date'], future_decrease['estimate'], 'rv', ms = 12, label = 'Pred. Decrease')\n",
    "\n",
    "        #errorbars\n",
    "        ax.errorbar(future['Date'].dt.to_pydatetime(), future['estimate'], \n",
    "                    yerr = future['upper'] - future['lower'], \n",
    "                    capthick=1.4, color = 'k',linewidth = 2,\n",
    "                   ecolor='darkblue', capsize = 4, elinewidth = 1, label = 'Pred with Range')\n",
    "\n",
    "        plt.legend(loc = 2, prop={'size': 10});\n",
    "        plt.xticks(rotation = '45')\n",
    "        plt.ylabel('Predicted Stock Price (US $)');\n",
    "        plt.xlabel('Date'); plt.title('Predictions for %s' % self.symbol);\n",
    "        plt.show()\n",
    "        \n",
    "    def changepoint_prior_validation(self, start_date=None, end_date=None,changepoint_priors = [0.001, 0.05, 0.1, 0.2]):\n",
    "\n",
    "\n",
    "        # 默认开始日期是数据结束前两年，结束日期是数据结束前一年\n",
    "        if start_date is None:\n",
    "            start_date = self.max_date - pd.DateOffset(years=2)\n",
    "        if end_date is None:\n",
    "            end_date = self.max_date - pd.DateOffset(years=1)\n",
    "            \n",
    "        # 转换为pandas datetime以索引数据帧\n",
    "        start_date = pd.to_datetime(start_date)\n",
    "        end_date = pd.to_datetime(end_date)\n",
    "        \n",
    "        start_date, end_date = self.handle_dates(start_date, end_date)\n",
    "                               \n",
    "        # 选择self.training_years年数\n",
    "        train = self.stock[(self.stock['Date'] > (start_date - pd.DateOffset(years=self.training_years))) & \n",
    "        (self.stock['Date'] < start_date)]\n",
    "        \n",
    "        # 测试数据由范围指定\n",
    "        test = self.stock[(self.stock['Date'] >= start_date) & (self.stock['Date'] <= end_date)]\n",
    "\n",
    "        eval_days = (max(test['Date']) - min(test['Date'])).days\n",
    "        \n",
    "        results = pd.DataFrame(0, index = list(range(len(changepoint_priors))), \n",
    "            columns = ['cps', 'train_err', 'train_range', 'test_err', 'test_range'])\n",
    "\n",
    "        print('\\nValidation Range {} to {}.\\n'.format(min(test['Date']),\n",
    "            max(test['Date'])))\n",
    "            \n",
    "        \n",
    "        # 迭代所有变更点并制作模型\n",
    "        for i, prior in enumerate(changepoint_priors):\n",
    "            results.loc[i, 'cps'] = prior\n",
    "            \n",
    "            #变更点\n",
    "            self.changepoint_prior_scale = prior\n",
    "            \n",
    "            # 使用指定的cps创建并训练模型\n",
    "            model = self.create_model()\n",
    "            model.fit(train)\n",
    "            future = model.make_future_dataframe(periods=eval_days, freq='D')\n",
    "                \n",
    "            future = model.predict(future)\n",
    "            \n",
    "            # 培训结果和指标\n",
    "            train_results = pd.merge(train, future[['ds', 'yhat', 'yhat_upper', 'yhat_lower']], on = 'ds', how = 'inner')\n",
    "            avg_train_error = np.mean(abs(train_results['y'] - train_results['yhat']))\n",
    "            avg_train_uncertainty = np.mean(abs(train_results['yhat_upper'] - train_results['yhat_lower']))\n",
    "            \n",
    "            results.loc[i, 'train_err'] = avg_train_error\n",
    "            results.loc[i, 'train_range'] = avg_train_uncertainty\n",
    "            \n",
    "            #测试结果和指标\n",
    "            test_results = pd.merge(test, future[['ds', 'yhat', 'yhat_upper', 'yhat_lower']], on = 'ds', how = 'inner')\n",
    "            avg_test_error = np.mean(abs(test_results['y'] - test_results['yhat']))\n",
    "            avg_test_uncertainty = np.mean(abs(test_results['yhat_upper'] - test_results['yhat_lower']))\n",
    "            \n",
    "            results.loc[i, 'test_err'] = avg_test_error\n",
    "            results.loc[i, 'test_range'] = avg_test_uncertainty\n",
    "\n",
    "        print(results)\n",
    "\n",
    "\n",
    "        \n",
    "        #培训和测试平均错误\n",
    "        self.reset_plot()\n",
    "        \n",
    "        plt.plot(results['cps'], results['train_err'], 'bo-', ms = 8, label = 'Train Error')\n",
    "        plt.plot(results['cps'], results['test_err'], 'r*-', ms = 8, label = 'Test Error')\n",
    "        plt.xlabel('Changepoint Prior Scale'); plt.ylabel('Avg. Absolute Error ($)');\n",
    "        plt.title('Training and Testing Curves as Function of CPS')\n",
    "        plt.grid(color='k', alpha=0.3)\n",
    "        plt.xticks(results['cps'], results['cps'])\n",
    "        plt.legend(prop={'size':10})\n",
    "        plt.show();\n",
    "        \n",
    "        #培训和测试平均不确定性\n",
    "        self.reset_plot()\n",
    "\n",
    "        plt.plot(results['cps'], results['train_range'], 'bo-', ms = 8, label = 'Train Range')\n",
    "        plt.plot(results['cps'], results['test_range'], 'r*-', ms = 8, label = 'Test Range')\n",
    "        plt.xlabel('Changepoint Prior Scale'); plt.ylabel('Avg. Uncertainty ($)');\n",
    "        plt.title('Uncertainty in Estimate as Function of CPS')\n",
    "        plt.grid(color='k', alpha=0.3)\n",
    "        plt.xticks(results['cps'], results['cps'])\n",
    "        plt.legend(prop={'size':10})\n",
    "        plt.show();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
